use super::cbase::CBase;
use super::{
    err, err_value, get_db, json, ok, throw_err, Base, Client, ECode, MapSv, Resp, VMap, Value,
};
use std::cmp::Ordering;
use std::collections::HashMap;

pub struct Query;

impl Base for Query {}
impl CBase for Query {}

impl Query {
    pub async fn do_cmd(&self, data: &MapSv, client: &Client) -> Resp {
        let (q, limit) = self.sql_parse(data)?;

        let tm = match data.k_bool("tm") {
            Some(v) => v,
            None => false,
        };

        let dtp = match data.k_bool("dtp") {
            Some(v) => v,
            None => true,
        };

        let db = get_db(data);
        let mut ret = MapSv::new();

        for cmd in q.iter() {
            let body = if tm {
                match client.send_query_tm(cmd, &db).await {
                    Ok(v) => v,
                    Err(_) => continue,
                }
            } else {
                match client.send_query(cmd, &db).await {
                    Ok(v) => v,
                    Err(_) => continue,
                }
            };

            self.build_body(body, &mut ret, tm, dtp, limit);
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    fn build_body(
        &self,
        body: Value,
        ret: &mut MapSv,
        tm: bool,
        dtp: bool,
        limit: usize,
    ) -> Option<()> {
        let body = body.k_array("results")?;

        for it in body.iter() {
            let it = it.as_object()?.k_first_obj("series")?;

            let name = it.k_str("name")?;
            let data = match self.sub_cont(it, tm, dtp, limit) {
                Some(v) => v,
                None => continue,
            };

            let (start, end) = match self.body_time(&data) {
                Some(v) => v,
                None => continue,
            };

            let cont = json!({
                "start": start,
                "end": end,
                "data": data
            });

            ret.insert(name.to_string(), cont);
        }

        Some(())
    }

    /* columns: [k1, k2, k3]
     * values: [
     *     [1, 2, 3],
     *     [4, 2, 7]
     * ]
     *  to: [
     *      {k1: 1, k2: 2, k3: 3},
     *      {k1: 4, k2: 2, k3: 7}
     *  ]
     */
    fn sub_cont<'b>(
        &self,
        body: &'b MapSv,
        tm: bool,
        dtp: bool,
        limit: usize,
    ) -> Option<Vec<HashMap<&'b str, Value>>> {
        let col = body.k_array("columns")?;
        let val = body.k_array("values")?;

        let mut cmp = Vec::new();
        let mut data = Vec::new();
        let mut pre = "".to_string();
        let mut cur = "".to_string();

        let mut count = 0;

        for v in val.iter() {
            let v = v.as_array()?;

            let mut tmp = HashMap::new();
            let mut ignore = false;

            for (i, k) in col.iter().enumerate() {
                let t = &v[i];
                if t.is_null() {
                    continue;
                }

                let k = k.as_str()?;
                if k == "nti" {
                    continue;
                }

                if dtp && k == "dtp" {
                    let dp = t.as_i64()?;
                    if dp > 1000 {
                        ignore = true;
                        break;
                    }
                }

                if k == "time" {
                    cur = if tm {
                        t.as_i64()?.to_string()
                    } else {
                        t.as_str()?.to_string()
                    };

                    tmp.insert(k, json!(cur));
                    continue;
                }

                tmp.insert(k, json!(t));
            } // for

            if ignore || tmp.is_empty() {
                continue;
            }

            if cur == pre {
                cmp.push(tmp);
            } else {
                if !cmp.is_empty() {
                    cmp.sort_unstable_by(|a, b| {
                        let (ati, bti) = match self.item_sort(a, b) {
                            Some(v) => v,
                            None => {
                                return Ordering::Greater;
                            }
                        };

                        ati.cmp(&bti)
                    });

                    data.append(&mut cmp);
                }

                cmp.push(tmp);
            }

            count += 1;
            if limit > 0 && count >= limit {
                break;
            }

            pre = cur.clone();
        } // for

        if !cmp.is_empty() {
            cmp.sort_unstable_by(|a, b| {
                let (ati, bti) = match self.item_sort(a, b) {
                    Some(v) => v,
                    None => {
                        return Ordering::Greater;
                    }
                };

                ati.cmp(&bti)
            });

            data.append(&mut cmp);
        }

        if limit > 0 && data.len() > limit {
            let _ = data.split_off(limit);
        }

        if !tm {
            for it in data.iter_mut() {
                it.remove("ti");
            }
        }

        Some(data)
    }

    fn item_sort(&self, x: &HashMap<&str, Value>, y: &HashMap<&str, Value>) -> Option<(i32, i32)> {
        let x: i32 = x.get("ti")?.as_str()?.parse().ok()?;
        let y: i32 = y.get("ti")?.as_str()?.parse().ok()?;

        Some((x, y))
    }

    fn body_time<'a>(
        &self,
        data: &'a Vec<HashMap<&'a str, Value>>,
    ) -> Option<(&'a Value, &'a Value)> {
        let start = data.first()?.get("time")?;
        let end = data.last()?.get("time")?;

        Some((start, end))
    }
}
